/// @file ShaderManager.cpp
/// @brief provides higher level and easier access to ngl ShaderManager
/// allows group actions on many shaders at a time, such as updating matrices from camera change

#include "boost/foreach.hpp"

#include "ngl/ShaderManager.h"

#include "ShaderManager.h"
#include "ShaderObject.h"
#include "Configuration.h"


ShaderManager::ShaderManager()
{
    //get an instance of the shader manager
    m_shaders = ngl::ShaderManager::instance();

    //read values from settings file
    std::vector<ShaderObject*>* myShaders = Configuration::initialiseShader();

    //load list of shaders
    BOOST_FOREACH(ShaderObject* item, *myShaders)
    {
        //save shader name
        m_names.push_back(item->getShaderName());

        if (!item->getIsSpecial())
        {
            //normal shader loading
            m_shaders->loadShader(item->getShaderName(), item->getVertexFile(), item->getFragementFile());
        }
        else
        {
            //special shader loading with attributes

            std::string vertexName = item->getShaderName() + "Vertex";
            std::string fragmentName = item->getShaderName() + "Fragment";

            m_shaders->createShaderProgram(item->getShaderName());

            m_shaders->attachShader(vertexName, ngl::VERTEX);
            m_shaders->attachShader(fragmentName, ngl::FRAGMENT);
            m_shaders->loadShaderSource(vertexName, item->getVertexFile());
            m_shaders->loadShaderSource(fragmentName, item->getFragementFile());

            m_shaders->compileShader(vertexName);
            m_shaders->compileShader(fragmentName);
            m_shaders->attachShaderToProgram(item->getShaderName(), vertexName);
            m_shaders->attachShaderToProgram(item->getShaderName(), fragmentName);

            for (int i = 0; i < item->getAttributes().size(); i++)
            {
                m_shaders->bindAttribute(item->getShaderName(), item->getAttributeValues()[i], item->getAttributes()[i]);
            }

            m_shaders->linkProgramObject(item->getShaderName());
        }


    }

    //deleting shaders pointers
    for (int i = 0; i < myShaders->size(); i++) delete (*myShaders)[i];

    myShaders->clear();
    delete myShaders;

}

void ShaderManager::updateViewProjection(ngl::Camera* _cam)
{
    //update view and projection matrices from cam info
    updateView(_cam);
    updateProjection(_cam);
}

void ShaderManager::updateView(ngl::Camera* _cam)
{
    for (int i = 0; i < m_names.size(); i++)
    {
        //update view matrix for all shaders from cam info
        m_shaders->useShader(m_names[i]);
        m_shaders->setShaderParamFromMatrix(m_names[i],"ViewMatrix", _cam->getModelView());
    }
}

void ShaderManager::updateProjection(ngl::Camera* _cam)
{
    for (int i = 0; i < m_names.size(); i++)
    {
        //update projection matrix for all shaders from cam info
        m_shaders->useShader(m_names[i]);
        m_shaders->setShaderParamFromMatrix(m_names[i],"projectionMatrix", _cam->getProjection());
    }
}

void ShaderManager::updateModel(const ngl::Matrix _matrix)
{
    for (int i = 0; i < m_names.size(); i++)
    {
        //update model matrix for all shaders from matrix
        m_shaders->useShader(m_names[i]);
        m_shaders->setShaderParamFromMatrix(m_names[i],"ModelMatrix", _matrix);
    }
}

void ShaderManager::updateModel
                        (
                            const std::string _name,
                            const ngl::Matrix _matrix,
                            const bool _exclusive
                        )
{
    if (_exclusive)
    {
        //called entirely on its own and not part of a chain of commands

        //enable the shader
        m_shaders->useShader(_name);
    }

    //update model matrix for the shader from matrix
    m_shaders->setShaderParamFromMatrix(_name,"ModelMatrix", _matrix);
}

void ShaderManager::updateColor
                            (
                                const std::string _name,
                                const ngl::Colour _colour,
                                const bool _exclusive
                            )
{
    if (_exclusive)
    {
        //called entirely on its own and not part of a chain of commands

        //flush the shader states
        glFlush();

        //enable the shader
        m_shaders->useShader(_name);
    }

    //set the colour param to the shader
    m_shaders->setShaderParam4f(_name, "Colour", _colour.m_r, _colour.m_g, _colour.m_b, _colour.m_a);
}

void ShaderManager::updateModelColor
                        (
                            const std::string _name,
                            const ngl::Matrix _matrix,
                            const ngl::Colour _colour
                        )
{
    //flush the shader states
    glFlush();

    //enable the shader
    m_shaders->useShader(_name);

    //update model info to shader
    updateModel(_name, _matrix, false);

    //update color into to shader
    updateColor(_name, _colour, false);
}
